//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<thread.cpp>
///	@path	~/src/lib/system/
///	@date	2007/09/10
///	@desc	Thread encapsulation.

#include "config/config.h"
#include "config/os.h"

#include "lib/system/sysinfo.h"
#include "lib/system/thread.h"
#include "lib/system/tls.h"
#include "lib/system/lock.h"

#include <process.h>

namespace xeres {

	namespace
	{
		class _TlsKey
		{
		public:
			//@ ctor
			_TlsKey( void )
			{
				m_key = ::TlsAlloc();
				if( m_key == TLS_OUT_OF_INDEXES )
				{
					__win32_error( _S("_TlsKey::CTOR: Failed to allocate TLS key.") );
					FATAL_ABORT( _S("Failed to allocate TLS key") );
				}
			}
			//@ dtor
			~_TlsKey( void )
			{
				::TlsFree( m_key );
			}
			// get key
			DWORD key( void ) const
			{
				return m_key;
			}
		private:
		//@ data
			DWORD m_key;
		};
	}

	// Thread tls note
	static _TlsKey s_threadTlsKey;
	static _TlsKey s_threadTlsName;

	namespace
	{
		class _MainThread : public Thread
		{
		public:

			//@ ctor
			_MainThread( void )
			{
				// insert tls
				::TlsSetValue( s_threadTlsKey.key() , this );
			}

			//@ dtor
			~_MainThread( void )
			{
				::TlsSetValue( s_threadTlsKey.key() , NULL );
			}

			/*!
				\brief		Execution of a thread.
				\return		Thread exit code.
			*/
			virtual uint32 Execute( void )
			{
				TRACE_ERROR( _S("_MainThread::Execute: Try to use 'Execute' of main thread.") );
				FATAL_ABORT( _S("Illegal usage of _MainThread.") );
				return 0;
			}
		};
	}

	// InitMain
	void Thread::InitMain( void )
	{
		static _MainThread s_MainThreadInstance;
		if( s_MainThreadInstance.m_thread == NULL )
		{
			s_MainThreadInstance.m_thread = (uintptr_t)::GetCurrentThread();
		}
	}

	// ctor
	Thread::Thread( void )
		: m_thread( NULL )
	{}

	// dtor
	Thread::~Thread( void )
	{
		if( m_thread )
			::CloseHandle( (HANDLE)m_thread );
	}

	// create a thread
	void Thread::Create( void )
	{
		TRACK_FUNCTION_SCOPE();

		// lock in order to prevent shadow start
		// a lock for thread operation
		static ThreadLock s_threadCreationLock;
		LockGuard<ThreadLock> locker( s_threadCreationLock );

		if( m_thread == NULL )
		{
			m_thread = ::_beginthreadex( NULL , 0 , Run , (LPVOID)this , CREATE_SUSPENDED , NULL );
			assert( m_thread );
		}
	}

	// start thread
	void Thread::Start( void )
	{
		TRACK_FUNCTION_SCOPE();

		if( m_thread == NULL )
		{
			TRACE_ERROR( _S("Thread::start: Thread is not created, cannot start.") );
			return;
		}
		if( ((DWORD)-1) == ::ResumeThread( (HANDLE)m_thread ) )
		{
			__win32_error( _S("Thread::start: ResumeThread call: %s.") );
		}
	}

	// determine if thread is running
	bool Thread::IsRunning( void ) const
	{
		TRACK_FUNCTION_SCOPE();

		// thread is not started
		if( m_thread == NULL )
			return false;

		// test to wait thread
		if( ::WaitForSingleObject( (HANDLE)m_thread , 0 ) == WAIT_TIMEOUT )
			return false;

		// still running
		return true;
	}

	// wait for the thread ends
	void Thread::Wait( void )
	{
		TRACK_FUNCTION_SCOPE();

		if( m_thread == NULL )
			return;

		// check! cannot be current thread.
		if( ::GetCurrentThread() == (HANDLE)m_thread )
		{
			TRACE_ERROR( _S("Thread::wait: Cannot wait thread itself.") );
			FATAL_ABORT( _S("Thread synchronization error.") );
		}

		::WaitForSingleObject( (HANDLE)m_thread , INFINITE );
	}

	bool Thread::Wait( const Time& time )
	{
		TRACK_FUNCTION_SCOPE();

		if( m_thread == NULL )
			return false;

		// check! cannot be current thread.
		if( ::GetCurrentThread() == (HANDLE)m_thread )
		{
			TRACE_ERROR( _S("Thread::wait: Cannot wait thread itself.") );
			FATAL_ABORT( _S("Thread synchronization error.") );
		}
		// filter out infinite situation
		if( time.ToMilliSecond() == INFINITE )
		{
			Wait();
			return true;
		}
		return ( ::WaitForSingleObject( (HANDLE)m_thread , (DWORD)time.ToMilliSecond() ) == WAIT_OBJECT_0 );
	}

	// set thread priority
	void Thread::SetPriority( Priority priority )
	{
		TRACK_FUNCTION_SCOPE();

		// check thread
		if( m_thread == NULL )
			return;

		if( ::SetThreadPriority( (HANDLE)m_thread , priority ) == FALSE )
		{
			__win32_error( _S("Thread::setPriority: Failed to set thread priority (%s).") );
		}
	}

	// get thread user time
	Time Thread::GetUserTime( void ) const
	{
		// check thread
		if( m_thread == NULL )
			return Time();
		
		FILETIME start , end , kernel , user;
		::GetThreadTimes( (HANDLE)m_thread , &start , &end , &kernel , &user );

		__int64 time;
		memcpy( &time , &user , sizeof(FILETIME) );

		return Time( (clock_t)time );
	}

	// get thread kernel time
	Time Thread::GetKernelTime( void ) const
	{
		// check thread
		if( m_thread == NULL )
			return Time();
		
		FILETIME start , end , kernel , user;
		::GetThreadTimes( (HANDLE)m_thread , &start , &end , &kernel , &user );

		__int64 time;
		memcpy( &time , &kernel , sizeof(FILETIME) );

		return Time( (clock_t)time );
	}

	// execution wrap
	uint32 __stdcall Thread::Run( void * data )
	{
		// setup thread info

		// set thread local storage

		// cast pointer
		Thread * pThis = static_cast<Thread*>( data );

		// insert tls
		::TlsSetValue( s_threadTlsKey.key() , pThis );

		int exitCode = 0;

		// setup stack overflow protection
		__try
		{
			exitCode = pThis->Execute();
		}
		__except( ::GetExceptionCode() == EXCEPTION_STACK_OVERFLOW )
		{
			_resetstkoflw();
			TRACE_ERROR( _S("Thread::run: Stack overflow detected.") );
		}

		// cleanup thread local storage
		//Tls::instance().OnThreadExit();
		::TlsSetValue( s_threadTlsKey.key() , NULL );

		return exitCode;
	}

	Thread * Thread::GetCurrent( void )
	{
		return static_cast<Thread*>( ::TlsGetValue( s_threadTlsKey.key() ) );
	}

	// SetThreadName
	void Thread::SetThreadName( const AString& name )
	{
		void * p = ::TlsGetValue( s_threadTlsName.key() );
		if( p )
			free( p );

		size_t len = sizeof(char) * ( name.size() + 1 );
		p = malloc( len );
		memcpy( p , name.c_str() , len );

		::SetThreadName( ::GetCurrentThreadId() , static_cast<const char *>(p) );

		::TlsSetValue( s_threadTlsName.key() , p );
	}
	// GetThreadName
	const char * Thread::GetThreadName( void )
	{
		void * p = ::TlsGetValue( s_threadTlsName.key() );
		if( p )
		{
			return static_cast<const char*>(p);
		}
		else
		{
			return "unnamed";
		}
	}

	// SingleAffinity
	void Thread::SingleAffinity( int no_processor )
	{
		if( m_thread == NULL )
			return;

		assert( no_processor < get_number_of_core() );

		std::vector<bool> process_affinity;
		get_available_core( process_affinity );

		if( process_affinity[no_processor] == false )
		{
			TRACE_ERROR( _S("Thread::SingleAffinity: Using an invalid core of process.") );
			return;
		}

		DWORD_PTR mask = (DWORD_PTR)1 << no_processor;
		if( ::SetThreadAffinityMask( (HANDLE)m_thread , mask ) == 0 )
		{
			__win32_error( _S("Thread::SingleAffinity: Failed to set thread affinity: %s.") );
		}
	}

	// MultiAffinity
	void Thread::MultiAffinity( const std::vector<bool>& affinity )
	{
		if( m_thread == NULL )
			return;

		std::vector<bool> process_affinity;
		get_available_core( process_affinity );

		DWORD mask = 0;

		for( size_t i = 0 , total = process_affinity.size() ; i < total ; ++i )
		{
			if( process_affinity[i] == false && affinity[i] == true )
			{
				TRACE_ERROR( _S("Thread::MultiAffinity: Using an invalid core of process.") );
				return;
			}

			if( affinity[i] )
			{
				mask |= 1 << i;
			}
		}
		if( mask == 0 )
		{
			TRACE_ERROR( _S("Thread::MultiAffinity: No core specified for thread to run.") );
			return;
		}
		if( ::SetThreadAffinityMask( (HANDLE)m_thread , mask ) == 0 )
		{
			__win32_error( _S("Thread::MultiAffinity: Failed to set thread affinity: %s.") );
		}
	}
	// ProcThread
	ProcThread::ProcThread( function<void(void)> proc )
		: m_proc( proc )
	{}

	// exec
	uint32 ProcThread::Execute( void )
	{
		TRACK_FUNCTION_SCOPE();

		// exec
		m_proc();
		return 0;
	}

} // namespace xeres
